import { Plugin } from "./components/Plugin";
import { TemplateHandler } from 'easy-template-x';
import * as fs from 'fs';
import { Content } from "./components/Content";
import { travel } from './utils';
import path = require("path");
import * as stream from 'stream';

interface Options {
    renderer?: 'stream' | 'string',
    output?: string;
    fileName?: string;
    stream?: stream.Writable
}

type Item = Content | string | number | boolean;
interface ItemData {
    [key: string]: Item | Array<ItemData>
}

export class DocxGenerator {
    plugins: Array<Plugin> = [];
    constructor() {
    }

    addPlugins(plugin: Plugin) {
        this.plugins.push(plugin);
    }

    async handleAsyncItemData(data: ItemData): Promise<any> {
        await Promise.all(Object.values(data).map(item => {
            if (Array.isArray(item)) {
                return Promise.all(item.map((subItem: ItemData) => this.handleAsyncItemData(subItem)));
            } else if (item instanceof Content) {
                return item.getResolve();
            } else {
                return Promise.resolve(item);
            }
        }));
    }

    beforeItemData(item: Content | string) {
        return this.plugins.reduce((c: Content | string, plugin: Plugin) => {
            return plugin.before(c);
        }, item);
    }
    afterItemData(item: Content | string) {
        return this.plugins.reduce((c: Content | string, plugin: Plugin) => {
            return plugin.after(c);
        }, item);
    }

    async render(docx: Buffer, data: ItemData, options?: Options) {
        const handle = new TemplateHandler();
        // before
        travel(data, this.beforeItemData.bind(this))
        
        // 异步
        await this.handleAsyncItemData(data);

        // after
        travel(data, this.afterItemData.bind(this))
        const listData = travel<ItemData, any>(data, function(item) {
            if (item instanceof Content) {
                return item.toJSON();
            }
            return item;
        });

        const doc = await handle.process(docx, listData);
        
        if (options.renderer === 'stream') {
            if (options.stream) {
                options.stream.write(doc);
                options.stream.end();
            } else {
                throw new Error('options.stream 错误');
            }
        } else if (options.output) {
            if (!fs.existsSync(options.output)) {
                throw new Error(`输出路径：${options.output} 不存在`);
            }
            fs.writeFileSync(path.resolve(options.output, (options.fileName || 'output') + '.docx'), doc);
        } else {
            throw new Error('options.output 不能为空');
        }
    }
}